
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Xml;
using System.Text.RegularExpressions;

using Swaf;
using Swaf.Call;
using Swaf.Resource;

namespace Swaf.Scheduler
{
	/// <summary>
	/// </summary>
	public class BaseScheduleTask : IScheduledTask, ICall
	{
		protected object m_syncObject = new object();
		protected string m_taskId;
		protected string m_frequency;
		protected bool m_isSingleton = false;
		protected bool m_isRunning = false;
		protected Match m_mr;
		protected DateTime m_nextFireTime;
		protected bool m_isRepeating;
		protected ICall m_internalCall;
		protected object[] m_params=null;
		protected IApplication m_app;

		public BaseScheduleTask(Match mr, object[] parms)
		{
			string id = (string) parms[0];
			if (parms[1] is XmlElement)
			{
				createFromElement(mr, id, (XmlElement) parms[1]);
			}
			else
			{
				createFromICall(mr, id, (ICall) parms[1], null);
			}
		}

		public bool IsSingleton
		{
			get { return m_isSingleton; }
			set { m_isSingleton = value; }
		}

		protected bool IsRunning
		{
			get
			{
				lock (m_syncObject)
				{
					return m_isRunning;					
				}
			}
			set
			{
				lock (m_syncObject)
				{
					m_isRunning = value;
				}
			}
		}
		public void createFromElement(Match mr, string id, XmlElement initInfo)
		{
			m_app = Application.currentApp;
			XmlElement callDef=null;
			foreach(XmlNode n in initInfo.ChildNodes)
			{
				if(n is XmlElement)
				{
					callDef = (XmlElement)n;
					break;
				}
			}
			m_mr = mr;
			init(initInfo.GetAttribute("id"), initInfo.GetAttribute("isSingleton"),callDef);
			moveToNextRepeatTime();
		}
		public void createFromICall(Match mr, string id, ICall call, object[] parms)
		{
			m_app = Application.currentApp;
			m_internalCall = call;
			m_taskId = id;
			m_mr = mr;
			m_frequency = m_mr.Groups[0].Value.ToLower();
			m_isRepeating = m_mr.Groups[1].Value.ToLower() != "in";
			m_params = parms;
			moveToNextRepeatTime();
			Application.currentApp.Log.println(2,"Defined Scheduled Task {0} with frequency of {1} to execute call type {2}",m_taskId,m_frequency,call.GetType().Name);

		}
		public BaseScheduleTask(Match mr, string id, ICall call, object[] parms)
		{
			createFromICall(mr, id, call, parms);
		}
		private void init(string id, string isSingleton, XmlElement callDef)
		{
			m_taskId = id;
			m_isSingleton = ((isSingleton == null) || (isSingleton == string.Empty))?false:Convert.ToBoolean(isSingleton);
			m_frequency = m_mr.Groups[0].Value.ToLower();
			m_isRepeating = m_mr.Groups[1].Value.ToLower() != "in";
			setupCall(callDef);
			Application.currentApp.Log.println(2,"Defined Scheduled Task {0} with frequency of {1} to execute call with definition ({2})",m_taskId,m_frequency,callDef.OuterXml);
		}
		private void setupCall(XmlElement callDef)
		{
			IClassFactory callFactory = Application.currentApp.factories["CallTypes"];
			m_internalCall = (ICall) callFactory.createFromNickName(callDef.Name, "", callDef);
			XmlNode paramsNode = callDef.SelectSingleNode("Params");
			if (paramsNode != null)
			{
				XmlNodeList callParamNodes = paramsNode.SelectNodes("Param");
				m_params = new object[callParamNodes.Count];
				for(int p = 0; p < m_params.Length; ++p)
				{
					XmlElement param = (XmlElement) callParamNodes[p];
					string type=param.GetAttribute("type");
					object val = null;
					Type thisType = null;
					//If there is a child node for this param, pass it in as the value.
					if((type == null) || type.Equals(""))
						thisType = typeof(object);
					else
						thisType = Type.GetType(type);
					if (thisType == null)
						throw new BaseException(String.Format("Unable to create param type {0} from param definition {1}",type,param.OuterXml));

					string tmpVal = param.InnerXml;
					val=Serialization.deserialize(type,tmpVal);
					m_params[p] = val;
				}
			}

		}
		public object Params
		{
			get
			{
				if (m_params != null && m_params.Length == 1)
					return m_params[0];
				else
					return m_params;
			}
		}
		#region IScheduledTask Members

		public virtual object fireNow()
		{
			// Ensure call isn't already running if this task is registered as a singleton
			if (IsSingleton && IsRunning)
			{
				return null;
			}

			object result = null;

			try
			{
				IsRunning = true;

				object parms = this.Params;
				if (parms != null)
					result = m_internalCall.call(parms);
				else
					result = m_internalCall.call();
			}
			finally
			{
				IsRunning = false;
			}

			return result;
		}

		public string Id
		{
			get
			{
				return m_taskId;
			}
		}

		public virtual bool IsReady
		{
			get
			{
				return  (DateTime.Now  >= m_nextFireTime);
			}
		}

		public void moveToNextRepeatTime()
		{
			m_nextFireTime = getNextExecutionTime(m_mr);
		}

		public bool IsRepeatable
		{
			get
			{
				return m_isRepeating;
			}
		}

		public bool IsSelfScheduling
		{
			get
			{
				return false;
			}
		}


		protected DateTime getNextExecutionTime(Match mr) 
		{
			DateTime curDate = DateTime.Now;
			DateTime nextDate= DateTime.Now;
			int hours=0;
			int minutes=0;
			int seconds=0;
			int dow=0;
			int dowNow=0;
			int newSkip = 0;
			int skip = 0;
			string freq = "";
			string time = "";
			Match mrTime=null;
			bool timeOnly=false;
			bool simpleTime=false;		
			try 
			{
				timeOnly = (mr.Groups.Count ==2);
				simpleTime = (mr.Groups.Count==4);
			
				if (timeOnly) 
				{
					//This is a simple at time pattern.	
					time = mr.Groups[1].Value;
				} 
				else 
				{
					skip = 1;
					if(mr.Groups[2].Value != null && mr.Groups[2].Value.Length > 0)
						skip = int.Parse(mr.Groups[2].Value);
					freq = mr.Groups[3].Value;
					if (simpleTime) 
					{
						
						//addMillisecondsToTime(cal, calcMilliseconds(skip, freq));
						return curDate + calcTimeSpan(skip, freq);
					} 
					else 
					{
						time = mr.Groups[4].Value;
					}
				}
				
				nextDate += timeToAdd(curDate,time);
				//If this is a time only pattern, we are done.
				if (timeOnly) 
				{
					return nextDate;	
				}
				
				dow= dayIndex(freq);
				dowNow=(int)curDate.DayOfWeek;
				if (nextDate > curDate && (freq.ToLower().Equals("day") || (dow==dowNow))) 
				{
					return nextDate;	
				}
				if (freq.ToLower().Equals("day")) 
				{
					nextDate += new TimeSpan(skip,0,0,0,0);
				} 
				else 
				{ //Must be weekday

					if (dowNow==dow) 
					{
						//Same day
						newSkip = skip;
					} 
					else 
					{
						if (dow > dowNow) 
						{
							newSkip = dow - dowNow;
						} 
						else 
						{
							newSkip = 7 - dowNow + dow;
						}
						if (skip > 1) 
						{
							newSkip = newSkip + (skip * 7);	
						}
					}
				
					nextDate += new TimeSpan(newSkip,0,0,0,0);
				}			
			} 
			catch (Exception e) 
			{
				m_app.Log.println("Error calculating next execution time {0}", e.ToString());
                throw new BaseException("Error calculating next execution time", e);
            }
		
			return nextDate;
		}
		protected int dayIndex(string dow)
		{
			string srchDay = dow.ToLower().Substring(0,3);
			if (srchDay.Equals("sun"))
				return 0;
			else if (srchDay.Equals("mon"))
				return 1;
			else if (srchDay.Equals("tue"))
				return 2;
			else if (srchDay.Equals("wed"))
				return 3;
			else if (srchDay.Equals("thu"))
				return 4;
			else if (srchDay.Equals("fri"))
				return 5;
			else if (srchDay.Equals("sat"))
				return 6;
			else if (srchDay.Equals("day"))
				return -1;
			else
				throw new PatternRepository.BadPattern(string.Format("{0} is not a valid day"));
		}
		protected TimeSpan calcTimeSpan(int timeLength, string timeType)
		{
			int amountOfTime = 0;
			int days = 0;
			int hours = 0;
			int minutes = 0;
			int seconds = 0;
			if (m_app.resMgr.matches("{sec}", timeType).Success)
				seconds = timeLength;
			else if (m_app.resMgr.matches("{minute}", timeType).Success)
				minutes = timeLength;
			else if (m_app.resMgr.matches("{hour}", timeType).Success)
				hours = timeLength;
			else if (m_app.resMgr.matches("{day}", timeType).Success)
				days = timeLength;
			else
				throw new PatternRepository.BadPattern(
					"Invalid frequency " + timeLength + " " + timeType);
			return new TimeSpan(days, hours, minutes, seconds, 0);
		}
	
		protected TimeSpan timeToAdd(DateTime fromTime, String time)
		{
			//GregorianCalendar newTime = (GregorianCalendar)fromTime.clone();
			
			DateTime newTime = new DateTime(fromTime.Ticks);
			Match mrTime = m_app.resMgr.matches("{splittime}", time);
			int hours = int.Parse(mrTime.Groups[1].Value);
			int minutes = int.Parse(mrTime.Groups[2].Value);
			int seconds = 0;
			if (mrTime.Groups.Count >= 4) 
			{
				try 
				{
					if (! mrTime.Groups[3].Value.Equals("" )) 
					{
						seconds = int.Parse(mrTime.Groups[3].Value);
					}
				} 
				catch (Exception e) 
				{
					//Seconds were not supplied, this has to be am/pm
					if (mrTime.Groups[3].Value.ToLower().Equals("pm")) 
					{
						hours += 12;
                        // Neet to ensure that if the time is 12 pm it doesn't become 24 but becomes 0 instead
                        hours %= 24;
                    }
				}
			}
			if (mrTime.Groups.Count == 5 && mrTime.Groups[4].Value.ToLower().Equals("pm"))
            {
                hours += 12;
                // Neet to ensure that if the time is 12 pm it doesn't become 24 but becomes 0 instead
                hours %= 24;
            }
			newTime = new DateTime(fromTime.Year,fromTime.Month,fromTime.Day,hours, minutes, seconds);
			return newTime - fromTime;
		}

		#endregion

		#region ICall Members

		public string Method {get{return m_internalCall.Method;}}
		public Swaf.Call.CallParamOption ParamHandling
		{
			get
			{
				// TODO:  Add BaseScheduleTask.ParamHandling getter implementation
				return new Swaf.Call.CallParamOption ();
			}
			set
			{
				// TODO:  Add BaseScheduleTask.ParamHandling setter implementation
			}
		}

		public bool WorksWithMultiCall
		{
			get
			{
				// TODO:  Add BaseScheduleTask.WorksWithMultiCall getter implementation
				return false;
			}
		}

		public object call(params object[] p)
		{
			return fireNow();
		}

		public object[] CallParams
		{
			get
			{
				// TODO:  Add BaseScheduleTask.CallParams getter implementation
				return null;
			}
			set
			{
				// TODO:  Add BaseScheduleTask.CallParams setter implementation
			}
		}

		public Swaf.Call.CallDistanceType CallDistance
		{
			get
			{
				return new Swaf.Call.CallDistanceType ();
			}
		}

		#endregion
	}
}
